package com.atguigu.tingshu.search.service.impl;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch._types.aggregations.LongTermsBucket;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.ChildScoreMode;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.CompletionSuggestOption;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.HitsMetadata;
import co.elastic.clients.elasticsearch.core.search.Suggestion;
import co.elastic.clients.json.JsonData;
import com.alibaba.fastjson.JSONObject;
import com.atguigu.tingshu.album.client.AlbumFeignClient;
import com.atguigu.tingshu.album.client.CategoryFeignClient;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.util.PinYinUtils;
import com.atguigu.tingshu.model.album.AlbumAttributeValue;
import com.atguigu.tingshu.model.album.AlbumInfo;
import com.atguigu.tingshu.model.album.BaseCategory3;
import com.atguigu.tingshu.model.album.BaseCategoryView;
import com.atguigu.tingshu.model.search.AlbumInfoIndex;
import com.atguigu.tingshu.model.search.AttributeValueIndex;
import com.atguigu.tingshu.model.search.SuggestIndex;
import com.atguigu.tingshu.model.user.UserInfo;
import com.atguigu.tingshu.query.search.AlbumIndexQuery;
import com.atguigu.tingshu.search.dao.AlbumInfoDao;
import com.atguigu.tingshu.search.dao.SuggestInfoDao;
import com.atguigu.tingshu.search.service.SearchService;
import com.atguigu.tingshu.user.client.UserInfoFeignClient;
import com.atguigu.tingshu.vo.album.AlbumInfoVo;
import com.atguigu.tingshu.vo.album.AlbumStatVo;
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import jakarta.annotation.Resource;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RBloomFilter;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.suggest.Completion;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;


@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class SearchServiceImpl implements SearchService {

    @Autowired
    private AlbumInfoDao albumInfoDao;

    @Autowired
    private AlbumFeignClient albumFeignClient;

    @Autowired
    private UserInfoFeignClient userInfoFeignClient;

    @Autowired
    private CategoryFeignClient categoryFeignClient;

    @Resource
    private ElasticsearchClient elasticsearchClient;

    @Resource
    private SuggestInfoDao suggestInfoDao;

    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;

    @Autowired
    private RBloomFilter rBloomFilter;



    /**
     * 将Elasticsearch中的专辑删除掉
     *
     * @param albumId
     */
    @Override
    public void removeAlbumFromElasticsearch(long albumId) {
        albumInfoDao.deleteById(albumId);
    }

    /**
     * 将专辑添加到Elasticsearch中
     *
     * @param albumId
     */
    @Override
    public void albumToElasticsearch(long albumId) {
        //实例化AlbumInfoIndex对象
        AlbumInfoIndex albumInfoIndex = new AlbumInfoIndex();
        //查询专辑的详细信息
        AlbumInfo albumInfo = albumFeignClient.getAlbumInfo(albumId);
        if (albumInfo == null) {
            return;
        }
        //补全属性
        albumInfoIndex.setId(albumId);
        albumInfoIndex.setAlbumTitle(albumInfo.getAlbumTitle());
        albumInfoIndex.setAlbumIntro(albumInfoIndex.getAlbumIntro());
        //设置作者名字
        UserInfo userInfo = userInfoFeignClient.getUserInfo(albumInfo.getUserId());
        if (userInfo != null && userInfo.getId() != null) {
            albumInfoIndex.setAnnouncerName(userInfo.getNickname());
        }
        albumInfoIndex.setCoverUrl(albumInfo.getCoverUrl());
        albumInfoIndex.setIncludeTrackCount(albumInfo.getIncludeTrackCount());
        albumInfoIndex.setIsFinished(albumInfo.getIsFinished().toString());
        albumInfoIndex.setPayType(albumInfo.getPayType());
        albumInfoIndex.setCreateTime(new Date());
        //设置专辑的一级分类,二级分类,三级分类
        BaseCategoryView baseCategoryView = categoryFeignClient.getBaseCategoryView(albumInfo.getCategory3Id());
        if (baseCategoryView != null) {
            albumInfoIndex.setCategory1Id(baseCategoryView.getCategory1Id());
            albumInfoIndex.setCategory2Id(baseCategoryView.getCategory2Id());
            albumInfoIndex.setCategory3Id(baseCategoryView.getCategory3Id());
        }
        //t设置专辑的统计信息
        Map<String, Integer> albumStatMap = albumFeignClient.getAlbumStat(albumId);
        //播放量
        Integer playStatNum = albumStatMap.get("0401");
        albumInfoIndex.setPlayStatNum(playStatNum);
        //订阅量
        Integer subscribeStatNum = albumStatMap.get("0402");
        albumInfoIndex.setSubscribeStatNum(subscribeStatNum);
        //购买量
        Integer buyStatNum = albumStatMap.get("0403");
        albumInfoIndex.setBuyStatNum(buyStatNum);
        //评论数
        Integer commentStatNum = albumStatMap.get("0404");
        albumInfoIndex.setCommentStatNum(commentStatNum);
        //计算热度值,播放1分,订阅3分,购买5分,评论3分
        Integer hotScore = playStatNum * 1 + subscribeStatNum * 3 + buyStatNum * 5 + commentStatNum * 3;
        albumInfoIndex.setHotScore(hotScore.doubleValue());

        //设置专辑的属性值
        List<AlbumAttributeValue> albumAttributeValueList = albumFeignClient.getAlbumAttributeValue(albumId);
        List<AttributeValueIndex> attributeValueIndexList = albumAttributeValueList.stream().map(albumAttributeValue -> {
            AttributeValueIndex attributeValueIndex = new AttributeValueIndex();
            attributeValueIndex.setAttributeId(albumAttributeValue.getAttributeId());
            attributeValueIndex.setValueId(albumAttributeValue.getValueId());
            return attributeValueIndex;
        }).collect(Collectors.toList());
        albumInfoIndex.setAttributeValueIndexList(attributeValueIndexList);

        //将数据保存到es
        albumInfoDao.save(albumInfoIndex);

        //将专辑的提示词存符es
        saveSuggestInfo(albumInfoIndex);
    }

    private void saveSuggestInfo(AlbumInfoIndex albumInfoIndex) {
        String albumTitle = albumInfoIndex.getAlbumTitle();
        if (StringUtils.isNotEmpty(albumTitle)) {

            //专辑标题
            saveSuggest(albumTitle, albumTitle);

            //作者名字
            String announcerName = albumInfoIndex.getAnnouncerName();
            if (StringUtils.isNotEmpty(announcerName)) {
                saveSuggest(albumTitle, announcerName);
            }

            //专辑描述
            String albumIntro = albumInfoIndex.getAlbumIntro();
            if (StringUtils.isNotEmpty(albumIntro)) {
                saveSuggest(albumTitle, albumIntro);
            }
        }
    }

    /**
     * 保存提示词到es
     *
     * @param albumTitle
     * @param keyword
     */
    private void saveSuggest(String albumTitle, String keyword) {
        SuggestIndex suggestIndex = new SuggestIndex();
        String id = UUID.randomUUID().toString().replaceAll("-", "");
        suggestIndex.setId(id);
        suggestIndex.setTitle(albumTitle);
        suggestIndex.setKeyword(new Completion(new String[]{keyword}));
        suggestIndex.setKeywordPinyin(new Completion(new String[]{PinYinUtils.toHanyuPinyin(keyword)}));
        suggestIndex.setKeywordSequence(new Completion(new String[]{PinYinUtils.getFirstLetter(keyword)}));
        suggestInfoDao.save(suggestIndex);
    }

    /**
     * 获取首页显示数据:
     * 1.查询到首页热门指定的7个三级分类
     * 2.根据7个三级分类id在es中查出这7个三级分类的全部专辑数据
     * 3.根据三级分类id进行分桶,获取每个三级分类对应的前6条热门数据
     *
     * @param category1Id
     * @return key-三级分类名称,value-三级分类排名前6的数据
     */
    @SneakyThrows
    @Override
    public List<Map<String, Object>> channel(Long category1Id) {
        //获取一级分类的指定的三级分类
        List<BaseCategory3> category3List = categoryFeignClient.findTopBaseCategory3(category1Id);
        //三级分类转map
        Map<Long, BaseCategory3> category3Map = category3List.stream().collect(Collectors.toMap(
                category3 -> category3.getId(),
                category3 -> category3
        ));
        //转换数据类型
        List<FieldValue> fieldValueList = category3List.stream().map(category3 -> FieldValue.of(category3.getId())
        ).collect(Collectors.toList());
        //拼接查询条件
        SearchRequest.Builder builder = new SearchRequest.Builder();
        builder.index("albuminfo_java0315");
        //构建查询条件
        builder.query(query -> query.terms(
                        terms -> terms.field("category3Id")
                                .terms(
                                        t -> t.value(fieldValueList)
                                )
                )
        );
        //聚合(分组)
        builder.aggregations("aggCategory3",//第一次分桶聚合的别名
                agg -> agg.terms(t -> t.field("category3Id"))//根据三级分类id进行分桶
                        .aggregations("topHitsResult",//子聚合
                                ag -> ag.topHits(t -> t.size(6)
                                        .sort(
                                                s -> s.field(
                                                        f -> f.field("hotScore").order(SortOrder.Desc)
                                                )
                                        )
                                )
                        )
        );
        //查询
        SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search(builder.build(), AlbumInfoIndex.class);
        //解析返回结果
        Map<String, Aggregate> aggregations = searchResponse.aggregations();
        if (aggregations != null) {
            return aggregations.get("aggCategory3").lterms().buckets().array().stream().map(
                    bucket -> {
                        //初始化返回对象
                        JSONObject c3Json = new JSONObject();
                        //获取三级分类id
                        long category3Id = bucket.key();
                        //获取三级分类对应的数据
                        List<AlbumInfoIndex> albumInfoIndexList =
                                bucket.aggregations().get("topHitsResult").topHits().hits().hits().stream().map(
                                        hit -> {
                                            return hit.source().to(AlbumInfoIndex.class);
                                        }
                                ).collect(Collectors.toList());
                        //封装返回数据
                        c3Json.put("baseCategory3", category3Map.get(category3Id));
                        c3Json.put("list", albumInfoIndexList);
                        return c3Json;
                    }
            ).collect(Collectors.toList());
        }
        //返回
        return null;
    }

    /**
     * 专辑搜索
     *
     * @param albumIndexQuery
     * @return
     */
    @SneakyThrows
    @Override
    public Object albumInfo(AlbumIndexQuery albumIndexQuery) {
        //拼接查询条件
        SearchRequest.Builder builder = initQuery(albumIndexQuery);
        //查询
        SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search(builder.build(), AlbumInfoIndex.class);
        //返回值解析
        AlbumSearchResponseVo result = getResult(searchResponse);
        //当前页
        result.setPageNo(albumIndexQuery.getPageNo());
        //页大小
        Integer pageSize = albumIndexQuery.getPageSize();
        result.setPageSize(pageSize);
        //总页数
        Long total = result.getTotal();
        result.setTotalPages(total % pageSize == 0 ? total / pageSize : (total / pageSize) + 1);
        //返回结果
        return result;
    }

    private AlbumSearchResponseVo getResult(SearchResponse<AlbumInfoIndex> searchResponse) {
        //实例化返回的vo
        AlbumSearchResponseVo vo = new AlbumSearchResponseVo();
        //命中的结果
        HitsMetadata<AlbumInfoIndex> hits = searchResponse.hits();
        long total = hits.total().value();
        vo.setTotal(total);
        List<AlbumInfoIndexVo> albumInfoIndexVoList = hits.hits().stream().map(
                hit -> {
                    //原始数据
                    AlbumInfoIndex albumInfoIndex = hit.source();
                    //高亮数据
                    Map<String, List<String>> highlight = hit.highlight();
                    if (highlight != null && highlight.size() > 0) {
                        List<String> albumTitleList = highlight.get("albumTitle");
                        if (albumTitleList != null && albumTitleList.size() > 0) {
                            String albumTitle = "";
                            for (String s : albumTitleList) {
                                albumTitle += s;
                            }
                            albumInfoIndex.setAlbumTitle(albumTitle);
                        }
                    }
                    //属性转移
                    AlbumInfoIndexVo albumInfoVo = new AlbumInfoIndexVo();
                    BeanUtils.copyProperties(albumInfoIndex, albumInfoVo);
                    return albumInfoVo;
                }
        ).collect(Collectors.toList());
        //设置属性
        vo.setList(albumInfoIndexVoList);
        return vo;
    }


    /**
     * 拼接查询条件
     *
     * @param albumIndexQuery
     * @return
     */
    private SearchRequest.Builder initQuery(AlbumIndexQuery albumIndexQuery) {
        //初始化请求对象
        SearchRequest.Builder builder = new SearchRequest.Builder();
        //指定要查询的索引
        builder.index("albuminfo_java0315");
        //构建条件查询对象
        BoolQuery.Builder boolQuery = new BoolQuery.Builder();
        //关键字条件
        String keyword = albumIndexQuery.getKeyword();
        if (StringUtils.isNotEmpty(keyword)) {
            boolQuery.should(q -> q.match(m -> m.field("albumTitle").query(keyword)))
                    .should(q -> q.match(m -> m.field("albumIntro").query(keyword)));
        }
        //一级分类条件
        Long category1Id = albumIndexQuery.getCategory1Id();
        if (category1Id != null) {
            boolQuery.filter(f -> f.term(t -> t.field("category1Id").value(category1Id)));
        }
        //二级分类条件
        Long category2Id = albumIndexQuery.getCategory2Id();
        if (category2Id != null) {
            boolQuery.filter(f -> f.term(t -> t.field("category2Id").value(category2Id)));
        }
        //三级分类条件
        Long category3Id = albumIndexQuery.getCategory3Id();
        if (category3Id != null) {
            boolQuery.filter(f -> f.term(t -> t.field("category3Id").value(category3Id)));
        }
        //标签条件
        List<String> attributeList = albumIndexQuery.getAttributeList();
        if (attributeList != null) {
            attributeList.stream().forEach(attribute -> {
                if (StringUtils.isNotEmpty(attribute)) {
                    //0-属性id  1-属性值id
                    String[] split = attribute.split(":");
                    boolQuery.filter(
                            f -> f.nested(n -> n.query(q -> q.bool(
                                            b -> b.must(m -> m.term(t -> t.field("attributeValueIndexList.attributeId").value(split[0])))
                                                    .must(m -> m.term(t -> t.field("attributeValueIndexList.valueId").value(split[1])))
                                    )).path("attributeValueIndexList")
                                    .scoreMode(ChildScoreMode.None))
                    );
                }
            });
        }
        builder.query(boolQuery.build()._toQuery());
        //排序:  0-域 1-规则
        String order = albumIndexQuery.getOrder();
        if (StringUtils.isEmpty(order)) {
            //没有传排序规则,按热度值排序
            builder.sort(s -> s.field(f -> f.field("hotScore").order(SortOrder.Desc)));
        }
        if (StringUtils.isNotEmpty(order)) {
            String[] split = order.split(":");
            switch (split[0]) {
                //按热度值排序
                case "1" -> builder.sort(s -> s.field(
                        f -> f.field("hotScore").order("desc".equals(split[1]) ? SortOrder.Desc : SortOrder.Asc))
                );
                //按播放量排序
                case "2" -> builder.sort(s -> s.field(
                        f -> f.field("playStatNum").order("desc".equals(split[1]) ? SortOrder.Desc : SortOrder.Asc))
                );
                //按发布时间排序
                case "3" -> builder.sort(s -> s.field(
                        f -> f.field("createTime").order("desc".equals(split[1]) ? SortOrder.Desc : SortOrder.Asc))
                );
                //默认情况按热度值排序
                default -> builder.sort(s -> s.field(
                        f -> f.field("hotScore").order(SortOrder.Desc))
                );
            }
        }

        //分页
        Integer pageNo = albumIndexQuery.getPageNo();
        Integer pageSize = albumIndexQuery.getPageSize();
        builder.from((pageNo - 1) * pageSize).size(pageSize);
        //高亮
        builder.highlight(h -> h.fields("albumTitle",
                f -> f.preTags("<font style=color:red>").postTags("</font>")));
        //返回
        return builder;
    }

    /**
     * 提示词
     *
     * @param keyword
     * @return
     */
    @SneakyThrows
    @Override
    public List<String> completeSuggest(String keyword) {
        //拼接查询条件
        SearchRequest.Builder builder = new SearchRequest.Builder();
        //指定索引
        builder.index("suggestinfo_java0315");
        //拼接条件
        builder.suggest(
                b -> b.suggesters(
                                "suggestKeyword",//别名,方便后面取结果
                                s -> s.prefix(keyword).completion(
                                        c -> c.field(
                                                "keyword").size(10).skipDuplicates(true).fuzzy(
                                                f -> f.fuzziness("auto")
                                        )
                                )
                        )
                        .suggesters(
                                "suggestKeywordPinyin",//别名,方便后面取结果
                                s -> s.prefix(keyword).completion(
                                        c -> c.field(
                                                "keywordPinyin").size(10).skipDuplicates(true).fuzzy(
                                                f -> f.fuzziness("auto")
                                        )
                                )
                        )
                        .suggesters(
                                "suggestkeywordSequence",//别名,方便后面取结果
                                s -> s.prefix(keyword).completion(
                                        c -> c.field(
                                                "keywordSequence").size(10).skipDuplicates(true).fuzzy(
                                                f -> f.fuzziness("auto")
                                        )
                                )
                        )
        );
        //查询
        SearchResponse<SuggestIndex> searchResponse = elasticsearchClient.search(builder.build(), SuggestIndex.class);
        //结果解析
        if (searchResponse != null) {
            //获取keyword命中的结果
            List<String> list1 = searchResponse.suggest().get("suggestKeyword").get(0).completion().options().stream().map(
                    option -> option.source().getTitle()
            ).collect(Collectors.toList());
            //获取拼音命中的结果
            List<String> list2 = searchResponse.suggest().get("suggestKeywordPinyin").get(0).completion().options().stream().map(
                    option -> option.source().getTitle()
            ).collect(Collectors.toList());
            //获取拼音首字母的命中结果
            List<String> list3 = searchResponse.suggest().get("suggestkeywordSequence").get(0).completion().options().stream().map(
                    option -> option.source().getTitle()
            ).collect(Collectors.toList());
            //合并结果并去重,限制返回10条
            return Stream.of(list1, list2, list3).flatMap(Collection::stream)
                    .collect(Collectors.toSet()).stream()//去重
                    .limit(10)//取10条数据
                    .collect(Collectors.toList());
        }
        //返回
        return null;
    }

    /**
     * 获取专辑详情信息
     *
     * @param albumId
     * @return
     */
    @Override
    public Object albumInfoDetail(Long albumId) {
        Map result = new ConcurrentHashMap();
//        //布隆过滤器中没有数据直接返回null
//        boolean contains = rBloomFilter.contains(albumId);
//        if (!contains) {
//            return null;
//        }
        //获取专辑详情
        CompletableFuture<AlbumInfo> future1 = CompletableFuture.supplyAsync(() -> {
            AlbumInfo albumInfo = albumFeignClient.getAlbumInfo(albumId);
            result.put("albumInfo", albumInfo);
            return albumInfo;
        }, threadPoolExecutor);

        //获取用户信息
        CompletableFuture<Void> future2 = future1.thenAcceptAsync(albumInfo -> {
            if (albumInfo==null || albumInfo.getId() == null){
                return ;
            }
            UserInfo userInfo = userInfoFeignClient.getUserInfo(albumInfo.getUserId());
            UserInfoVo userInfoVo = new UserInfoVo();
            BeanUtils.copyProperties(userInfo, userInfoVo);
            result.put("announcer",userInfoVo);
        }, threadPoolExecutor);

        //获取分类信息
        CompletableFuture<Void> future3 = future1.thenAcceptAsync(albumInfo -> {
            if (albumInfo==null || albumInfo.getId() == null){
                return ;
            }
            BaseCategoryView baseCategoryView = categoryFeignClient.getBaseCategoryView(albumInfo.getCategory3Id());
            result.put("baseCategoryView",baseCategoryView);
        }, threadPoolExecutor);

        //获取统计信息
        CompletableFuture<Void> future4 = future1.thenAcceptAsync(albumInfo -> {
            if (albumInfo==null || albumInfo.getId() == null){
                return ;
            }
            Map<String, Integer> albumStat = albumFeignClient.getAlbumStat(albumInfo.getId());
            AlbumStatVo albumStatVo = new AlbumStatVo();
            albumStatVo.setAlbumId(albumInfo.getId());
            albumStatVo.setPlayStatNum(albumStat.get(SystemConstant.ALBUM_STAT_PLAY));
            albumStatVo.setSubscribeStatNum(albumStat.get(SystemConstant.ALBUM_STAT_SUBSCRIBE));
            albumStatVo.setBuyStatNum(albumStat.get(SystemConstant.ALBUM_STAT_BROWSE));
            albumStatVo.setCommentStatNum(albumStat.get(SystemConstant.ALBUM_STAT_COMMENT));
            result.put("albumStatVo",albumStatVo);
        }, threadPoolExecutor);

        //等待线程都执行
        CompletableFuture.allOf(future2,future3,future4).join();
        return result;

    }
}
